import datetime
import os.path
import queue
import threading
import time
from typing import Callable, Dict

from apscheduler.executors.pool import ProcessPoolExecutor
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.schedulers.background import BackgroundScheduler

import db_util
from conn_manager import ConnectionManager
from db_util import insert_or_update_live_room_batch
from logger import logger
from req import WsMsg, LiveRoom

handlers: Dict[str, Callable] = {}
# bag_pools: Dict[str, BagInfo] = {}  # 福袋池，按剩余时间升序，时间少的在前
# client_devices: Dict[str:list[Dict[str, str]]] = {}  # client_id, ip list

insert_interval_s = 1  # 直播间入库间隔，默认1秒

# 创建后台调度器
# scheduler = AsyncIOScheduler()

jobstores = {
    # 'mongo': {'type': 'mongodb'},
    'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
}
executors = {
    'default': {'type': 'threadpool', 'max_workers': 100},
    'processpool': ProcessPoolExecutor(max_workers=5)
}
job_defaults = {
    'coalesce': True,
    'max_instances': 50
}
scheduler = BackgroundScheduler()
scheduler.configure(jobstores=jobstores, executors=executors, job_defaults=job_defaults, )
scheduler.start()
conn_manager = ConnectionManager()


# def run_gold_task(link,url,gold):
#     print(f"task...{link} {url} {gold}")
#
# # room: LiveRoom = LiveRoom()
# # room.room_link = "https://vm.tiktok.com/ZMMwCHA89/"
# # room.scheme_url = "snssdk1233://live?room_id=7371661371078429446"
# # room.gold_num = "10"
# # tri_date = datetime.datetime.now() + datetime.timedelta(seconds=3)
# # scheduler.add_job(run_gold_task, 'date',
# #                   run_date=tri_date,
# #                   args=[room.room_link, room.scheme_url, room.gold_num])
# #
# # time.sleep(5)
class ScheduleService:

    def __init__(self):
        self.insert_queue = queue.Queue()
        dat_dir = "dat"
        if os.path.exists(dat_dir):
            os.mkdir(dat_dir)
        # print(f"数据保存到={os.path.abspath(dat_dir)}")
        handlers['ping'] = self.ping
        handlers['bag_info'] = self.handle_bag_info
        handlers['reg_devices'] = self.reg_devices
        handlers['update_status'] = self.update_status
        self.consumer = threading.Thread(target=self.listen_lazy_insert)
        self.consumer.start()

    def run(self):
        """todo 监控状态并调度"""

    def handle_bag_info(self, msg: WsMsg) -> WsMsg:
        room: LiveRoom = LiveRoom()
        d = msg.data
        room.bag_rem_time = d.get("bag_rem_time")
        room.room_link = d.get("room_link")
        room.scheme_url = d.get("scheme_url")
        room.room_id = d.get("room_id")
        room.gold_num = d.get("gold_num")
        room.datatime = d.get("datatime")

        # {"room_link": room_link, "scheme_url": scheme_url, "room_id": room_id, "gold_num": bag_gold,
        # "datatime": datatime, "bag_rem_time": bag_time, }
        try:
            return WsMsg(cmd=msg.cmd + "_response", data=self.save_live_room(room)[1])
        except Exception as e:
            logger.error(e)
            return WsMsg.error(cmd=msg.cmd + "_response", req_id=msg.req_id, msg=e)

    def save_live_room(self, room: LiveRoom) -> (bool, str):
        """保存福袋信息：直播间链接、room_id、剩余时间"""
        # insert_or_update_live_room(dat)
        if self.insert_queue.qsize() >= 50000:
            logger.debug("最多50000个直播间")
            return False, "最多50000个直播间"
        expire_time = self.get_expire_time(room.datatime, room.bag_rem_time)
        room.expire_time = expire_time
        self.insert_queue.put(room)
        # room.gold_num
        # 提前进入直播间等待
        s = 60
        run_date = expire_time - datetime.timedelta(seconds=s)
        if run_date > datetime.datetime.now():
            logger.debug(f"添加调度任务-抢币,任务触发时间={run_date}（提前{s}秒），过期时间={expire_time}")
            scheduler.add_job(gold_task, 'date',
                              run_date=run_date,  # todo 测试 立即执行
                              # run_date= datetime.datetime.now()+ datetime.timedelta(seconds=3), #todo 测试 立即执行
                              args=[room.room_link, room.scheme_url, room.gold_num, room.expire_time])

        else:
            logger.debug("运行时间过期")
        return True, "成功"

    def ping(self, msg: WsMsg):
        return WsMsg("ping", data="pong")

    def reg_devices(self, msg: WsMsg):
        logger.debug(f"handle reg_devices {msg.data}")
        # todo userId
        # data = [('user_id0000',msg.token, d.get('row'), d.get('box_ip'), d.get('ip'), d.get('status'), d.get('client_id'),) for d in msg.data]
        data = msg.data
        token = msg.token
        # insert_device_batch(data)
        user_id = "user_id-0000"
        db_util.insert_or_update_device(user_id, token, data)
        return WsMsg(msg.cmd, msg.req_id, msg.token, "done")

    def update_status(self, msg: WsMsg):
        logger.debug(f"handle update_status {msg.data}")
        ip_ = msg.data['ip']
        status = msg.data['status']
        db_util.update_device_status((ip_,), status, msg.token)
        return WsMsg(msg.cmd, msg.req_id, msg.token, "done")

    def dispatch(self, msg: WsMsg) -> WsMsg:
        """下发任务到终端"""
        logger.debug(f"token={msg.token}")
        # handlers=[]
        try:
            if msg.cmd not in handlers:
                return WsMsg.error(f"未定义编码=[{msg.cmd}]的处理器")
        except Exception as e:
            return WsMsg.error(f"请求格式错误:{e}")
        return handlers.get(msg.cmd)(msg)

    def listen_lazy_insert(self):
        """延迟1秒插入直播间"""
        while True:
            time.sleep(insert_interval_s)
            qsize = self.insert_queue.qsize()
            # print(f".. {qsize}")
            if qsize == 0:
                continue
            # print(f"到点插入 ")
            ll = []
            start = datetime.datetime.now()
            while qsize > 0:
                ll.append(self.insert_queue.get(block=False))
                qsize -= 1
                if len(ll) % 10000 == 0:
                    insert_or_update_live_room_batch(ll)
                    ll.clear()
            if len(ll) > 0:
                insert_or_update_live_room_batch(ll)
                ll.clear()
            print(f"耗时={datetime.datetime.now() - start}")

    def get_expire_time(self, look_time, bag_time):
        look_time = datetime.datetime.strptime(look_time, '%Y-%m-%d %H:%M:%S')
        run_date = look_time + datetime.timedelta(minutes=float(bag_time.split(":")[0]),
                                                  seconds=float(bag_time.split(":")[1]))
        return run_date
    #


def gold_task(link, url, gold, expire_time):
    """ 根据直播间金币数分派设备，下发抢金币脚本。"""
    # print(f"{link} {url} {gold}")
    logger.debug(f"下发抢金币任务 {link} {url} {gold}")
    devices = db_util.get_idle_devices(gold)
    logger.debug(f"空闲设备数={len(devices)}")
    for device in devices:
        token = device.token
        msg = WsMsg("switch", "", token, [
            {
                "ip": device.ip,
                "device_id": device.id,
                "script": "WaitOpenScript",  # 切换tk还是快手Kw
                "payload": {
                    "url_scheme": url,
                    "room_link": link,
                    "gold": gold,
                    "expire_time": expire_time.strftime("%Y-%m-%d %H:%M:%S"),
                }
            }
        ])
        logger.debug(f"下发抢金币 deviceIp={device.ip} token= {token}")
        conn_manager.send_with_token(msg)
    # db_util.update_device_status(tuple([int(d.id) for d in devices]), TaskStatus.SCHEDULING.value)
